
package adoc;

import adoc.xml.*;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipException;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.jdom.Element;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
 *
 * @author ILikePancakes
 */
public class AdocUtilities {
    public static int levelCount = 0;
    // schemos
    public static final String MANIFEST_SCHEMA_LOCATION = "manifest.xsd";
    public static final String RELATIONS_SCHEMA_LOCATION = "relations.xsd";
    public static final String SIGNED_SCHEMA_LOCATION = "signed.xsd";
    public static final String UNSIGNED_SCHEMA_LOCATION = "unsigned.xsd";
    public static final String SIGNED_SCHEMA_LOCATION_ALL = "signed_all.xsd";
    public static final String UNSIGNED_SCHEMA_LOCATION_ALL = "unsigned_all.xsd";
    // kuriamu failu path'ai
    public static final String SIGNED_PATH = "metadata/signed.xml";
    public static final String UNSIGNED_PATH = "metadata/unsigned.xml";
    public static final String MANIFEST_PATH = "META-INF/manifest.xml";
    public static final String RELATIONS_PATH = "META-INF/relations.xml";
    public static final String THUMBNAIL_PATH = "Thumbnails/thumbnail.png";
    public static final String MIMETYPE_PATH = "mimetype";
    // mime tipai
    public static final String MIMETYPE_ADOC = "application/vnd.lt.archyvai.adoc-2008";
    public static final String MIMETYPE_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    public static final String MIMETYPE_ODT = "application/vnd.oasis.opendocument.text";
    public static final String MIMETYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    public static final String MIMETYPE_ODS = "application/vnd.oasis.opendocument.spreadsheet";
    public static final String MIMETYPE_PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    public static final String MIMETYPE_PPSX = "application/vnd.openxmlformats-officedocument.presentationml.slideshow";
    public static final String MIMETYPE_ODP = "application/vnd.oasis.opendocument.presentation";
    public static final String MIMETYPE_PDF = "application/pdf";
    public static final String MIMETYPE_TIF = "image/tif";
    public static final String MIMETYPE_TIFF = "image/tiff";
    public static final String MIMETYPE_TIFX = "image/tiff-fx";
    public static final String MIMETYPE_JPEG = "image/jpeg";
    public static final String MIMETYPE_XML = "text/xml";
    public static final String MIMETYPE_PNG = "image/png";
    public static final String MIMETYPE_ZIP = "application/zip";
    /**
     * 
     * @param adocName      - kuriamo adoc path'as
     * @param mainDocument  - pagrindinio dokumento path'as
     * @param appendices    - priedų path'ų masyvas
     * @param attachments   - pridedamų dokumentų path'ų masyvas
     * @param appendixMap   - priedų priedų map'as; pvz. appendixMap.get(appendices[0]) grąžintų pirmo priedo priedų ArrayList'ą
     * @return ar pavyko sukurti adoc'ą, false, jei tas failas jau egzistuoja
     * @throws IOException
     */
    public static boolean createADOC(String adocName, String[] inputValues,  String mainDocument, String[] appendices,
            String[] attachments, HashMap<String, ArrayList> appendixMap) throws IOException {
        File file = new File(adocName);
        if (file.exists()) return false;
        // atributu ir metaduomenu failu pavadinimai bei deskriptoriai
        String[] metadata = new String[]{SIGNED_PATH, UNSIGNED_PATH};
        String[] METAINF = new String[]{MANIFEST_PATH, RELATIONS_PATH};
        File[] files = new File[metadata.length+METAINF.length];
        // mimetype failas
        File mimetype = File.createTempFile(MIMETYPE_PATH, null);
        BufferedWriter writer = new BufferedWriter(new FileWriter(mimetype));
        writer.write(MIMETYPE_ADOC);
        writer.close();
        // sukuriami tusti failai pagal nurodytus vardus ir issaugomi ju deskriptoriai
        for (int i = 0; i < files.length; i++) { files[i] = File.createTempFile("temp", null); }
        // sioje vietoje turetu buti pradines informacijos i failus surasymas bei issaugojimas
        Document[] documents = new Document[]{
            new ManifestDocument(mainDocument, appendices, appendixMap, attachments, metadata, METAINF),
            new RelationsDocument(mainDocument, appendices, appendixMap, attachments, metadata, METAINF),
            new SignedDocument(inputValues[0], inputValues[1]),
            new UnsignedDocument()
        };
        for (int i = 0; i < METAINF.length+metadata.length; i++) { writeXML(documents[i], files[i]); }
        // sukuriamas pats adoc'as, kuris atidaromas failu irrasymui
        Zip zip = new Zip(adocName, "w");
        // irasomi atributai ir metaduomenys
        for (int i = 0; i < METAINF.length; i++) zip.addFile(files[i], METAINF[i]);
        for (int i = 0; i < metadata.length; i++) zip.addFile(files[METAINF.length+i], metadata[i]);
        // irasomi priedai ir ju priedai
        if (appendices != null) {
            for (int i = 0; i < appendices.length; i++) zip.addFile(appendices[i], "priedai\\"+getFileName(appendices[i]));
            for (ArrayList aa : appendixMap.values()) for (Object a : aa) zip.addFile(a.toString(), "priedai\\"+getFileName(a.toString()));
        }
        // irasomi prideti dokumentai
        if (attachments != null) {
            for (int i = 0; i < attachments.length; i++) zip.addFile(attachments[i], "pridedami\\"+getFileName(attachments[i]));
        }
        // irasoma pagrindine laikmena
        zip.addFile(mainDocument, getFileName(mainDocument));
        // irasomas mimetype failas
        zip.addFile(mimetype, MIMETYPE_PATH);
        // baigiamas kurti failas
        zip.make();
        // istrinami laikini failai
        for (File temp : files) AdocUtilities.deleteFile(temp);
        AdocUtilities.deleteFile(mimetype);
        return true;

    }

    public static void writeXML(Document document, File file) throws FileNotFoundException, IOException {
        XMLOutputter out = new XMLOutputter();
        out.setFormat(Format.getPrettyFormat());
        java.io.FileWriter writer = new java.io.FileWriter(file);
        out.output(document, writer);
        writer.flush();
        writer.close();
    }

    public static void writeXML(Document document, String filename) throws FileNotFoundException, IOException { writeXML(document, new File(filename)); }

    public static String getFileName(String filename) { return new File(filename).getName(); }

    public static File createNewFile(String parentName, String filename) throws IOException {
        File file = new File(parentName, filename);
        if (file.getParent() != null) new File(file.getParent()).mkdirs();
        file.createNewFile();
        return file;
    }

    public static File createTempFile(String filename, int UID) throws IOException {
        File file = createNewFile(getWorkingPath(UID), filename);
        file.deleteOnExit();
        return file;
    }

    public static void deleteFile(File file) {
        if (file.isDirectory()) {
            String[] children = file.list();
            for (int i = 0; i < children.length; i++) { deleteFile(new File(file, children[i])); }
        }
        file.delete();
    }

    /*
     * Grazina direktorija, i kuria bus laikinai isarchyvuojamas atidaromas zip'as. UID nurodo laikinosios direktorijos vardo modifikatorių.
     * */
    public static String getWorkingPath(int UID) { return getWorkingPathWindows(UID).replace('\\', '/'); }
    public static String getRelativePath(File file, int UID) { 
        if (!file.getPath().equals(getWorkingPathWindows(UID)))
            return file.getPath().substring(getWorkingPathWindows(UID).length()+1).replace('\\', '/');
        else return "/";
    }
    public static String getRelativePathParent(String path, int UID) {
        String filename = new File(getWorkingPath(UID), path).getParent().replace('\\', '/');
        if (!filename.equals(getWorkingPath(UID)))
            return new File(getWorkingPath(UID), path).getParent().replace('\\', '/').substring(getWorkingPath(UID).length()+1);
        else return "/";
    }
    public static String getWorkingPathWindows(int UID) { return new File(System.getProperty("java.io.tmpdir"), "adoc"+UID).getPath(); }

    /**
     *
     * @param filename ADOC failo vardas
     * @return rastų klaidų masyvas
     */
    public static AdocFile openADOC(String filename) throws FileNotFoundException, IOException, JDOMException {
        ArrayList<String> errors = checkADOC(filename);
        if (errors.size() > 0) return new AdocFile(errors);
        else {
            // gauti reikiamus duomenis ir perduoti atvaizdavimui, jei netingesiu
            return new AdocFile(new ArrayList());
        }
    }

    public static ArrayList<String> checkADOC(String filename) throws FileNotFoundException, IOException, JDOMException  {
        int UID = AdocUtilities.levelCount;
        AdocUtilities.levelCount++;
        Zip zip = new Zip(filename, "r", UID);
        File adocDir = new File(getWorkingPath(UID));
        // isvalome, jei kas nors like nuo anksciau
        deleteFile(adocDir);
        adocDir.mkdir();
        ArrayList<String> errors = new ArrayList();
        try {
            ArrayList<String> zipEntryNames = zip.extract();
            errors = _checkADOC(filename, adocDir, zipEntryNames, zip, UID);
        } catch (ZipException ex) { errors.add("Pakuotė negali būti apsaugota slaptažodžiais."); }
        // isvalome
        deleteFile(adocDir);
        return errors;
    }

    /**
     *
     * @param adocDir         - naujai kuriamo adoc direktorija
     * @param adocFiles       - failu vardai kartu su savo path'ais paciame zipe, pvz. jei zipe yra META-INF katalogas,
     * kuriame yra manifest.xml failas, tuomet tam failui entryName bus META-INF\manifest.xml
     * @param zip             - Zip archyvo objektas jo formato tikrinimui
     */
    private static ArrayList<String> _checkADOC(String adocFilename, File adocDir, ArrayList<String> adocFiles, Zip zip, int UID)  throws IOException {
        ArrayList<String> errors = new ArrayList();
        ArrayList<String> filenames = getFilenamesInWorkingPath(UID); // laiko ir failus ir direktorijas
        Long sizeLimit = Long.valueOf("4294967296");
        int manifestSkippable = 1;
        int relationsSkippable = 1;
        File thumbnailFile = new File(getWorkingPath(UID), THUMBNAIL_PATH);
        File mimetypeFile = new File(getWorkingPath(UID), MIMETYPE_PATH);
        if (!allSchemasExist()) { errors.add("adoc validavimui nerasta viena ar daugiau schemų"); return errors; }
        for (String filename : filenames) { if (filename.split("/").length > 4)  { errors.add("hierarchinė failų struktūra negali būti didesnė nei 3 lygmenų"); break; } }
        if (thumbnailFile.exists()) {
            // manifestSkippable += 2; // jei cia uzkomentuota, tuomet turi buti Thumbnails/ ir Thumbnails/thumbnail.png irasai manifest.xml faile
            if (!getRealMimeType(thumbnailFile).equals(MIMETYPE_PNG)) { errors.add("thumbnail failas neleistino formato"); }
        }
        if (mimetypeFile.exists()) {
            BufferedReader reader = new BufferedReader(new FileReader(mimetypeFile));
            String content = reader.readLine();
            reader.close();
            if (!content.equals(MIMETYPE_ADOC)) errors.add("klaidinga mimetype informacija");
            manifestSkippable++;
            relationsSkippable++;
        } else { errors.add("nerastas mimetype failas"); }
        if (!AdocUtilities.getExtension(adocFilename).equalsIgnoreCase("ADOC")) errors.add("blogas failo extension'as");
        // 72.1. ar pakuotės rinkmena neviršija 4 GB dydžio apribojimų;
        if (zip.getSize() > sizeLimit) errors.add("pakuotės rinkmena viršija 4 GB dydžio apribojimą");
        // 72.2. ar pakuotės rinkmena yra ZIP archyvo rinkmena;
        if (!zip.isValid()) { errors.add("pakuotės rinkmena nėra ZIP archyvo rinkmena"); return errors; }
        if (filenames.size() > 65535) { errors.add("bendras elektroninio dokumento pakuotėje saugomų rinkmenų ir hierarchinės struktūros katalogų skaičius neturi viršyti 65535"); }
        for (String filename : filenames) { if (filename.length() > 65535) errors.add("ilgiausios nuorodos į rinkmeną ilgis neturi viršyti 65535 simbolių"); }
        // 72.3. ar ZIP archyvo rinkmenoje yra visos privalomosios elektroninio dokumento dalys:
        ManifestDocument manifest = null;
        RelationsDocument relations = null;
        //File metadataDir = null;
        //File main = null;
        HashMap<String, File> adocMap = new HashMap();
        // 72.3.5. ar yra elektroninio dokumento rinkmenų ir jų tipų aprašo rinkmena (manifest.xml);
        // 72.4. ar elektroninio dokumento rinkmenų ir jų tipų aprašo rinkmena (manifest.xml) atitinka šiuos Specifikacijos reikalavimus:
        if (filenames.contains(MANIFEST_PATH)) {
            // 72.4.1. ar atitinka elektroninio dokumento rinkmenų ir jų tipų aprašo rinkmenos XML schemą (17 priedo 4 punktas);
            // 72.4.2. ar rinkmena yra META-INF kataloge;
            if (isValidXML(getWorkingPath(UID)+"/"+MANIFEST_PATH, MANIFEST_SCHEMA_LOCATION)) {
                try {
                    manifest = new ManifestDocument(getWorkingPath(UID) + "/"+MANIFEST_PATH);
                } catch (JDOMException ex) { errors.add("klaida apdorojant manifest.xml"); }
                // 72.4.3. ar rinkmenoje nurodytos visos elektroninį dokumentą sudarančios rinkmenos ir katalogai, išskyrus rinkmenas manifest.xml ir mimetype;
                ArrayList<String> manifestFiles = manifest.fileNames();
                if (manifest.entryCount() != getFileCount(adocDir)-manifestSkippable) errors.add("manifest.xml rinkmenoje nenurodytos visos elektroninį dokumentą sudarančios rinkmenos ir katalogai");
                if (hasDuplicates(manifestFiles)) errors.add("manifest.xml turi besidubliuojančių SourcePart elemenų");
                for (String filename : filenames) {
                    File file = new File(getWorkingPath(UID) + "/" + filename);
                    if (file.isFile()) {
                        String mime = getRealMimeType(file);
                        String manifestMime = manifest.getType(filename);
                        if ((filename.equals(MANIFEST_PATH)) || (filename.equals(MIMETYPE_PATH) || (mime.equals(MIMETYPE_ZIP) && manifestMime.equals(MIMETYPE_ADOC)))) continue;
                        if (manifestMime == null) { errors.add("manifest.xml nerastas failas: "+filename+"/"); continue; }
                        // 72.4.4. ar visos elektroninį dokumentą sudarančios dalys atitinka Specifikacijos 9 priede nurodytus rinkmenų (katalogų) tipų žymėjimo reikalavimus;
                        if (!mime.equals(manifestMime) || !isValidType(mime)) errors.add("el. dokumento dalis neatitinka 9 priede nurodyto tipo žymėjimo " +
                                "(arba tipas neleidžiamas, arba jis nesutampa su nurodytu manifest.xml): "+file.getName());
                        if (!manifestFiles.contains(filename)) errors.add("manifest.xml rinkmenoje nenurodytos visos elektroninį dokumentą sudarančios rinkmenos " +
                                "ir katalogai: " + file.getName());
                    } else {
                        String manifestMime = manifest.getType(filename+"/");
                        if (manifestMime == null) { errors.add("manifest.xml nerastas katalogas: "+filename+"/"); continue; }
                        if (filename.equals("") && (manifestMime.equals(MIMETYPE_ADOC))) {
                        } else if (manifestMime.equals(ManifestDocument.METADATA_FOLDER)) {
                            if (adocMap.get("metadata") != null) { errors.add("manifest.xml nurodytas daugiau nei vienas metadata katalogas");
                            } else {
                                adocMap.put("metadata", file);
                                if (!file.getParent().equals(getWorkingPathWindows(UID))) errors.add("metadata katalogas klaidingoje vietoje (turi būti šakniniame kataloge)");
                            }
                        } else if (!manifestMime.equals("")) { errors.add("nurodytas nežinomas arba draudžiamas mime tipas direktorijai: "+filename+"/"+", tipas = "+manifestMime); }
                    }
                }
                if (adocMap.get("metadata") == null) errors.add("manifest.xml nenurodytas metaduomenų katalogas");
            } else { errors.add("rinkmenų ir jų tipų aprašo rinkmena neatitinka XML schemos"); return errors; }
        } else { errors.add("nerasta elektroninio dokumento rinkmenų ir jų tipų aprašo rinkmena (manifest.xml)"); return errors; }
        // 72.3.6. ar yra elektroninio dokumento ryšių aprašo rinkmena (relations.xml).
        //72.5. ar elektroninio dokumento ryšių aprašo rinkmena (relations.xml) atitinka šiuos Specifikacijos reikalavimus:
        if (filenames.contains(RELATIONS_PATH)) {
            // 72.5.1. ar atitinka elektroninio dokumento ryšių aprašo rinkmenos XML schemą (17 priedo 3 punktas);
            // 72.5.2. ar yra visi pakuotės struktūrą nusakantys ryšiai ir ar jie atitinka nustatytuosius reikalavimus;
            if (isValidXML(getWorkingPath(UID)+"/"+RELATIONS_PATH, RELATIONS_SCHEMA_LOCATION)) {
                try {
                    relations = new RelationsDocument(getWorkingPath(UID) + "/"+RELATIONS_PATH);
                } catch (JDOMException ex) { errors.add("klaida apdorojant relations.xml"); }
                // 72.3.1. ar yra pagrindinio dokumento rinkmena;
                try {
                    adocMap.put("main", new File(getWorkingPath(UID), relations.getMainDocumentPath()));
                    if (!adocMap.get("main").exists() || (!adocMap.get("main").getParent().equals(getWorkingPathWindows(UID)))) throw new JDOMException();
                } catch (JDOMException ex) {
                    errors.add("nerasta pagrindinio dokumento rinkmena arba yra daugiau nei viena");
                }
                // 72.3.2. ar yra bent viena pasirašomųjų metaduomenų rinkmena;
                List<String> signedList = relations.getFullSignedAttributePaths(UID);
                if (signedList != null) {
                    if (signedList.size() == 0) errors.add("nėra bent vienos pasirašomųjų metaduomenų rinkmenos");
                    else {
                        String signedDir = new File(signedList.get(0)).getParent();
                        if (adocMap.get("metadata") != null)
                            if (!signedDir.equals(adocMap.get("metadata").getPath())) errors.add("manifest.xml klaidinga informacija apie metaduomenų katalogą");
                        if (!new File(signedDir).getParent().equals(getWorkingPathWindows(UID))) errors.add("pasirašomieji metaduomenys klaidingoje direktorijoje");
                        for (String path : signedList) {
                            File signedFile = new File(path);
                            if (!signedFile.exists() || !signedFile.getParent().equals(signedDir)) 
                                errors.add("nėra visų pakuotės struktūrą nusakančių ryšių " +
                                        "(blogas kelias iki failo arba ne visi pasirašomųjų metaduomenų failai vienoje direktorijoje): "+path);
                        }
                    }
                } else { errors.add("nėra bent vienos pasirašomųjų metaduomenų rinkmenos"); }
                // 72.3.3. ar yra bent viena nepasirašomųjų metaduomenų rinkmena;
                List<String> unsignedList = relations.getFullUnsignedAttributePaths(UID);
                if (unsignedList != null) {
                    if (unsignedList.size() == 0) errors.add("nėra bent vienos nepasirašomųjų metaduomenų rinkmenos");
                    else {
                        String unsignedDir = new File(unsignedList.get(0)).getParent();
                        if (adocMap.get("metadata") != null)
                            if (!unsignedDir.equals(adocMap.get("metadata").getPath())) errors.add("manifest.xml klaidinga informacija apie metaduomenų katalogą");
                        if (!new File(unsignedDir).getParent().equals(getWorkingPathWindows(UID))) errors.add("nepasirašomieji metaduomenys klaidingoje direktorijoje");
                        for (String path : unsignedList) {
                            File unsignedFile = new File(path);
                            if (!unsignedFile.exists() || !unsignedFile.getParent().equals(unsignedDir))
                                errors.add("nėra visų pakuotės struktūrą nusakančių ryšių " +
                                        "(blogas kelias iki failo arba ne visi nepasirašomųjų metaduomenų failai vienoje direktorijoje): "+path);
                        }
                    }
                } else { errors.add("nėra bent vienos nepasirašomųjų metaduomenų rinkmenos"); }
                // 72.5.2. ar yra visi pakuotės struktūrą nusakantys ryšiai ir ar jie atitinka nustatytuosius reikalavimus;
                if (adocFiles.size()-relationsSkippable > relations.getAllPaths().size()) errors.add("yra ryšiais neidentifikuojamų turinio dalių arba dublikatų");
                for (Element relationship : relations.getAllRelationships()) {
                    String path = relationship.getAttributeValue("full-path");
                    if (!adocFiles.contains(path)) errors.add("nerastas ryšys: "+path);
                    else {
                        String type = relationship.getAttributeValue("type");
                        if (!AdocUtilities.isValidRelationType(AdocUtilities.getWorkingPath(UID)+"/"+path, type)) {
                            errors.add("failui '"+path+"' nurodytas neleistinas ryšys: "+relationship.getAttributeValue("type"));
                        } else { // validus relationship, t.y. "sis failas pagal nurodyta relationship turi tinkama mime tipa", taigi ziurime toliau;
                            String parentPath = relations.getSourcePartFullPath(relationship);
                            if (parentPath.equals("/")) {
                                if ((!type.equals(RelationsDocument.RELATIONSHIP_MAIN)) && (!type.equals(RelationsDocument.RELATIONSHIP_SIGNED))
                                        && (!type.equals(RelationsDocument.RELATIONSHIP_UNSIGNED)) && (!type.equals(RelationsDocument.RELATIONSHIP_THUMBNAIL))
                                        && (!RelationsDocument.isCustomType(type)))
                                    errors.add("klaidingas '/' ryšys relations.xml: "+path+" turi ryšį su "+parentPath);
                            } else if (relations.isMainDocument(parentPath)) {
                                if ((!type.equals(RelationsDocument.RELATIONSHIP_APPENDIX)) && (!type.equals(RelationsDocument.RELATIONSHIP_ATTACHMENT))
                                        && (!RelationsDocument.isCustomType(type)))
                                    errors.add("klaidingas MAIN ryšys relations.xml: "+path+" turi ryšį su "+parentPath);
                            } else if (relations.isAppendix(parentPath) || relations.referencesAppendix(path)) { // jei gali buti priedu priedu priedai, tuomet prideti =  || relations.referencesAppendix(path)
                                if (!type.equals(RelationsDocument.RELATIONSHIP_APPENDIX) && (!RelationsDocument.isCustomType(type)))
                                    errors.add("klaidingas APPENDIX ryšys relations.xml: "+path+" turi ryšį su "+parentPath);
                            } else if (relations.isAttachment(parentPath)) {
                                if (!RelationsDocument.isCustomType(type))
                                    errors.add("klaidingas ATTACHMENT ryšys relations.xml: "+path+" turi ryšį su "+parentPath);
                            } else {
                                if (!RelationsDocument.isCustomType(type))
                                    errors.add("klaidingas ryšys relations.xml (ciklas arba blogas ryšio tipas): "+path+" turi ryšį su "+parentPath);
                            }
                            // tikrinimas del priedu ar pridetiniu dokumentu katalogo atitikimo reikalavimams - sis negali buti sakninis, t.y. "top level"
                            if (type.equals(RelationsDocument.RELATIONSHIP_APPENDIX) || type.equals(RelationsDocument.RELATIONSHIP_ATTACHMENT)) {
                                if (new File(AdocUtilities.getWorkingPath(UID)+"/"+path).getParent().equals(getWorkingPathWindows(UID))) { errors.add("priedas ar pridėtinis dokumentas šakniniame kataloge"); }
                            }
                        }
                    }
                }
                if (adocMap.get("metadata") != null) {
                    for (File f : AdocUtilities.getFilesInDirectory(adocMap.get("metadata"))) {
                        if (f.isDirectory()) continue;
                        Element sourcePart = relations.getRootSourcePart();
                        String path = AdocUtilities.getRelativePath(f, UID);
                        if (!(relations.containsRelationship(sourcePart, path, RelationsDocument.RELATIONSHIP_SIGNED))
                                && !(relations.containsRelationship(sourcePart, path, RelationsDocument.RELATIONSHIP_UNSIGNED)))
                            errors.add("metadata direktorijoje esantys duomenys nera susieti metaduomenu rysiu");
                    }
                }
                for (String fullPath : relations.getAllFullPaths()) { if (!new File(getWorkingPath(UID)+"/"+fullPath).exists()) errors.add("klaidingas full-path: "+fullPath); }
                if (hasDuplicates(relations.getSourcePartFullPaths())) errors.add("relations.xml turi besidubliuojančių SourcePart elemenų");
                for (Element sourcePart : relations.getSourceParts()) {
                    List<Element> relationships = relations.getRelationshipsBySourcePart(sourcePart);
                    for (int i = 0; i < relationships.size()-1; i++) {
                        for (int j = i+1; j< relationships.size(); j++) {
                            Element relationships_1 = relationships.get(i);
                            Element relationships_2 = relationships.get(j);
                            if ((relationships_1.getAttributeValue("full-path").equals(relationships_2.getAttributeValue("full-path")))
                                    && (relationships_1.getAttributeValue("type").equals(relationships_2.getAttributeValue("type"))))
                                errors.add("relations.xml klaida: negali būti besidubliuojančių Relationship elementų ("+relationships_1.getAttributes()+")");
                        }
                    }
                }
                // 72.6. ar metaduomenų rinkmenos atitinka metaduomenų rinkmenoms keliamus reikalavimus:
                // 72.6.1. ar metaduomenų rinkmenos atitinka XML schemas (17 priedo 1 skyrius);
                for (String signed : signedList) if (!isValidXML(signed, SIGNED_SCHEMA_LOCATION)) errors.add("pasirašomųjų metaduomenų rinkmenos neatitinka XML schemų: "+getFileName(signed));
                for (String unsigned : unsignedList) if (!isValidXML(unsigned, UNSIGNED_SCHEMA_LOCATION)) errors.add("nepasirašomųjų metaduomenų rinkmenos neatitinka XML schemų: "+getFileName(unsigned));
                File fullSignedFile = File.createTempFile("temp", null);
                File fullUnsignedFile = File.createTempFile("temp2", null);
                try {
                    SignedDocument signed = new SignedDocument(signedList);
                    AdocUtilities.writeXML(signed, fullSignedFile);
                } catch (Exception e) { errors.add("iškilo problema validuojant pasirašomųjų metaduomenų rinkmenas pagal schemas"); }
                if (!isValidXML(fullSignedFile.getPath(), SIGNED_SCHEMA_LOCATION_ALL)) errors.add("pasirašomųjų metaduomenų rinkmenos neatitinka XML schemų");
                try {
                    UnsignedDocument unsigned = new UnsignedDocument(unsignedList);
                    AdocUtilities.writeXML(unsigned, fullUnsignedFile);
                } catch (Exception e) { errors.add("iškilo problema validuojant nepasirašomųjų metaduomenų rinkmenas pagal schemas"); }
                if (!isValidXML(fullUnsignedFile.getPath(), UNSIGNED_SCHEMA_LOCATION_ALL)) errors.add("nepasirašomųjų metaduomenų rinkmenos neatitinka XML schemų");
                // istriname laikinus failus
                deleteFile(fullSignedFile);
                deleteFile(fullUnsignedFile);
                //73. Išsamus elektroninio dokumento turinio patikrinimas apima šiuos etapus:
                //73.1. dokumento turinio dalių tarpusavio ryšių patikrinimas:
                List<String> appendixPaths = relations.getAppendixPaths();
                List<String> attachmentPaths = relations.getAttachmentPaths();
                List<String> aaPaths = relations.getAAPaths();
                //73.1.1. ar elektroniniame dokumente yra tik viena pagrindinio dokumento rinkmena;
                    // zr. 72.3.1.
                //73.1.2. ar vienareikšmiškai identifikuojamos dokumento turinio dalys, t. y., ar yra ryšiais neidentifikuojamų turinio dalių;
                // zr. 72.5.2
                //ar pagrindinio dokumento rinkmena nėra kurios nors kitos dalies priedas ar pridedamas dokumentas;
                if (aaPaths != null) {
                    for (String path : aaPaths){
                        try {
                            if (path.equals(relations.getMainDocumentPath())) {
                                errors.add("pagrindio dokumento rinkmena yra kitos dalies pridedamas dokumentas arba priedas");
                                break;
                            }
                        } catch (JDOMException ex) {}
                    }
                }
                if (attachmentPaths != null) {
                    for (String path : attachmentPaths){
                        try {
                            if (path.equals(relations.getMainDocumentPath())) {
                                errors.add("pagrindinio dokumento rinkmena yra pati sau pridedamas dokumentas");
                                break;
                            }
                        } catch (JDOMException ex) {}
                        //ar pridedamas dokumentas nėra kurios nors dalies priedas;
                        if (appendixPaths != null) if (appendixPaths.contains(path)) { errors.add("pridedamas dokumentas yra kitos dalies priedas"); break; }
                    }
                }
                if (appendixPaths != null) {
                    for (String path : appendixPaths) {
                        try {
                            if (path.equals(relations.getMainDocumentPath())) {
                                errors.add("pagrindinio dokumento rinkmena yra pati sau priedas");
                                break;
                            }
                        } catch (JDOMException ex) {}
                        //ar priedas nėra kurios nors dalies pridedamas dokumentas;
                        if (attachmentPaths != null)
                            if (attachmentPaths.contains(path)) { errors.add("priedas yra kitos dalies pridedamas dokumentas"); break; }
                    }
                }
        //73.1.3. ar pridedamų dokumentų rinkmenos (jei jų yra) ryšio tipu „Pridedamo dokumento rinkmena“ siejamos tik su pagrindinio dokumento rinkmena;
                if (attachmentPaths != null) {
                    if (relations.getAllAttachmentPaths().size() != attachmentPaths.size())
                        errors.add("yra pridedamų dokumentų, kurie ryšio tipu 'Pridedamo dokumento rinkmena' siejami NE TIK su pagrindinio dokumento rinkmena");
                }
        //73.1.4. ar pagrindinio dokumento rinkmena ir visos dokumento priedų rinkmenos (jei jų yra) pagal ryšio tipą „Priedo (priedų) rinkmena“ sudaro tvarkingą hierarchinę struktūrą, kurios
        //viršuje yra pagrindinio dokumento rinkmena, t. y., ar hierarchija vienintelė (apima visas priedų rinkmenas), ar nėra ciklų ir nuorodų į tą pačią rinkmeną.
        //73.2. turinio tipų patikrinimas:
        //73.2.1. ar rinkmenų ir jų tipų apraše aprašytos visos elektroninio dokumento turinį sudarančios rinkmenos;
        //73.2.2. ar dokumento priedų ir pridedamų dokumentų rinkmenos atitinka turinio tipų reikalavimus;
        //73.3. turinio dalių naudojamumo patikrinimas: ar visos elektroninio dokumento turinį sudarančios rinkmenos atitinka deklaruojamą formatą, neapsiribojant rinkmenų plėtinių patikrinimu.
            System.out.println("viskas ok");
            } else { errors.add("ryšių aprašo rinkmena neatitinka XML schemos"); return errors; }
        } else { errors.add("nerasta elektroninio dokumento ryšių aprašo rinkmena (relations.xml)"); return errors; }
        
        return errors;
    }

    /*
     * Grazina visus failus nurodytoje direktorijoje bei joje esanciose direktorijose
     * */
    public static ArrayList<File> getFilesInDirectory(File dir) {
        ArrayList<File> files = new ArrayList();
        if (dir.isDirectory()) {
            String[] children = dir.list();
            files.add(dir);
            for (int i = 0; i < children.length; i++) { files.addAll(getFilesInDirectory(new File(dir, children[i]))); }
        } else {
            files.add(dir);
        }
        return files;
    }

    public static ArrayList<File> getFilesInWorkingPath(int UID) { return getFilesInDirectory(new File(getWorkingPath(UID))); }

    public static ArrayList<String> getFilenamesInWorkingPath(int UID) {
        ArrayList<File> files = getFilesInDirectory(new File(getWorkingPath(UID)));
        ArrayList<String> filenames = new ArrayList();
        for (File file : files) {
            String path = "";
            if (!file.getPath().equals(getWorkingPathWindows(UID))) path = file.getPath().substring(getWorkingPathWindows(UID).length()+1);
            filenames.add(path.replace('\\', '/'));
        }
        return filenames;
    }

    /* Grazina failu ir direktoriju skaiciu direktorijoje + 1 (pati direktorija)
     * */
    public static int getFileCount(File dir) {
        int count = 0;
        if (dir.isDirectory()) {
            String[] children = dir.list();
            count = 1;
            for (int i = 0; i < children.length; i++) {
                count += getFileCount(new File(dir, children[i]));
            }
        } else {
            return 1;
        }
        return count;
    }

    public static boolean isValidXML(String filename, String schemaLocation) {
        File xmlDocument = new File(filename);
        File schemaDocument = new File(schemaLocation);
        try {
            SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
            Schema schema = factory.newSchema(schemaDocument);
            Validator validator = schema.newValidator();
            ErrorHandlerImpl errorHandler = new ErrorHandlerImpl();
            validator.setErrorHandler(errorHandler);
            StreamSource streamSource = new StreamSource(xmlDocument);
            validator.validate(streamSource);
            if (errorHandler.validationError == true) {
                System.out.println("XML Document has Error:" + errorHandler.validationError + " " + errorHandler.saxParseException.getMessage());
                return false;
            } else {
                return true;
            }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    }

    public static boolean hasIDError(String filename, String schemaLocation) {
        File xmlDocument = new File(filename);
        File schemaDocument = new File(schemaLocation);
        try {
            SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
            Schema schema = factory.newSchema(schemaDocument);
            Validator validator = schema.newValidator();
            ErrorHandlerImpl errorHandler = new ErrorHandlerImpl();
            validator.setErrorHandler(errorHandler);
            StreamSource streamSource = new StreamSource(xmlDocument);
            validator.validate(streamSource);
            if (errorHandler.validationError == true) {
                String error = errorHandler.saxParseException.getMessage();
                if (error.contains("attribute 'ID'")) return true;
                else return false;
            } else {
                return false;
            }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    }

    private static boolean isValidRelationType(String path, String relationship) {
        String mimeType = AdocUtilities.getRealMimeType(path);
        if (relationship.equals(RelationsDocument.RELATIONSHIP_MAIN) || relationship.equals(RelationsDocument.RELATIONSHIP_APPENDIX)) {
            return AdocUtilities.isValidDocumentType(mimeType);
        } else if (relationship.equals(RelationsDocument.RELATIONSHIP_SIGNED) || relationship.equals(RelationsDocument.RELATIONSHIP_UNSIGNED)) {
            return mimeType.equals(AdocUtilities.MIMETYPE_XML);
        } else if (relationship.equals(RelationsDocument.RELATIONSHIP_ATTACHMENT)) {
            if (mimeType.equals(AdocUtilities.MIMETYPE_ZIP)) {
                try {
                    ArrayList e = AdocUtilities.checkADOC(path);
                    if (e == null) { return false; } else { if (e.size() != 0) { return false; } }
                } catch (Exception e) { return false; }
                return true;
            }
        } else if (relationship.equals(RelationsDocument.RELATIONSHIP_THUMBNAIL)) {
            return mimeType.equals(AdocUtilities.MIMETYPE_PNG);
        }
        // return false;
        // lieka tik nezinomas rysys
        return true;
    }
    
    /*
     * Paziurima, ar nurodytas mime tipas yra leistinas.
     * */
    public static boolean isValidType(String mimetype) {
        String[] mimetypes = new String[] {
            MIMETYPE_ADOC, MIMETYPE_DOCX, MIMETYPE_ODT, MIMETYPE_XLSX, MIMETYPE_ODS, MIMETYPE_PPTX,
            MIMETYPE_PPSX, MIMETYPE_ODP, MIMETYPE_PDF, MIMETYPE_TIF, MIMETYPE_TIFF, MIMETYPE_TIFX, MIMETYPE_JPEG, MIMETYPE_XML, MIMETYPE_PNG
        };
        for (String m : mimetypes) { if (m.equals(mimetype)) return true; }
        return false;
    }
    /*
     * Paziurima, ar nurodytas mime tipas yra leistinas pagrindiniam dokumentui arba priedui.
     * */
    public static boolean isValidDocumentType(String mimetype) {
        String[] mimetypes = new String[] {
            MIMETYPE_DOCX, MIMETYPE_ODT, MIMETYPE_XLSX, MIMETYPE_ODS, MIMETYPE_PPTX,
            MIMETYPE_PPSX, MIMETYPE_ODP, MIMETYPE_PDF, MIMETYPE_TIF, MIMETYPE_TIFF, MIMETYPE_TIFX, MIMETYPE_JPEG, MIMETYPE_PNG
        };
        for (String m : mimetypes) { if (m.equals(mimetype)) return true; }
        return false;
    }

    public static String getExtension(String filename) { return filename.substring(filename.lastIndexOf(".")+1, filename.length()).toUpperCase(); }

    public static String getMimeByExtension(String extension) {
        if (extension.equals("DOCX")) return MIMETYPE_DOCX;
        else if (extension.equals("ODT")) return MIMETYPE_ODT;
        else if (extension.equals("XLSX")) return MIMETYPE_XLSX;
        else if (extension.equals("ODS")) return MIMETYPE_ODS;
        else if (extension.equals("PPTX")) return MIMETYPE_PPTX;
        else if (extension.equals("PPSX")) return MIMETYPE_PPSX;
        else if (extension.equals("ODP")) return MIMETYPE_ODP;
        else if (extension.equals("PDF")) return MIMETYPE_PDF;
        else if (extension.equals("TIF")) return MIMETYPE_TIF;
        else if (extension.equals("TIFF")) return MIMETYPE_TIFF;
        else if (extension.equals("TIFX")) return MIMETYPE_TIFX;
        else if ((extension.equals("JPEG")) || (extension.equals("JPG")) || (extension.equals("JFIF"))) return MIMETYPE_JPEG;
        else if (extension.equals("PNG")) return MIMETYPE_PNG;
        else return "";
    }
    /*
     *  grazina tikra mimetype nepriklausomai nuo extensiono
     * */
    public static String getRealMimeType(File file) {
        FileInputStream is = null;
        String mimetype = null;
        try {
        is = new FileInputStream(file);
        ContentHandler contenthandler = new BodyContentHandler();
        Metadata metadata = new Metadata();
        metadata.set(Metadata.RESOURCE_NAME_KEY, file.getName());
        Parser parser = new AutoDetectParser();
        // OOXMLParser parser = new OOXMLParser();
        parser.parse(is, contenthandler, metadata);
        mimetype = metadata.get(Metadata.CONTENT_TYPE);
        }
        catch (Exception e) {
          e.printStackTrace();
        }
        finally {
            if (is != null) try { is.close(); } catch (IOException ex) {}
        }
        // apdoroti mime tipa, kad sutaptu su specifikacijoje nurodomais
        return processMimeType(mimetype);
    }
    // apdoroti mime tipa, kad sutaptu su specifikacijoje nurodomais - NEBAIGTA
    private static String processMimeType(String mimetype) {
        if (mimetype.equals("application/xml")) return "text/xml";
        return mimetype;
    }
    
    public static String getRealMimeType(String filename) { return getRealMimeType(new File(filename)); }

    public static boolean hasDuplicates(List list) {
        Set<String> set = new HashSet<String>(list);
        if(set.size() < list.size()) return true;
        else return false;
    }

    private static boolean allSchemasExist() {
        String[] schemas = new String[] {MANIFEST_SCHEMA_LOCATION, RELATIONS_SCHEMA_LOCATION, SIGNED_SCHEMA_LOCATION, UNSIGNED_SCHEMA_LOCATION,
    SIGNED_SCHEMA_LOCATION_ALL, UNSIGNED_SCHEMA_LOCATION_ALL};
        for (String schema : schemas) if (!new File(schema).exists()) return false;
        return true;
    }

    private static class ErrorHandlerImpl extends DefaultHandler
    {
        public boolean  validationError = false;
        public SAXParseException saxParseException=null;
        @Override
        public void error(SAXParseException exception) throws SAXException
        {
            validationError = true;
            saxParseException=exception;
        }
        @Override
        public void fatalError(SAXParseException exception) throws SAXException
        {
            validationError = true;
            saxParseException=exception;
        }
        @Override
        public void warning(SAXParseException exception) throws SAXException {}
    }
}
